<!DOCTYPE html>
<title>MathMLElement GlobalEventHandlers</title>
<link rel="author" title="Brian Kardell" href="mailto:bkardell@igalia.com" />
<link rel="help" href="https://mathml-refresh.github.io/mathml-core/#dom-and-javascript"/>
<link rel="help" href="https://html.spec.whatwg.org/multipage/#event-handler-idl-attributes"/>
<link rel="help" href="https://html.spec.whatwg.org/multipage/#event-handler-content-attributes"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/WebIDLParser.js"></script>

<div style="display: none" id="container"></div>

<script>
  "use strict";

  // The prefixed animation events are special; their event types are
  // camel-case.
  const prefixedAnimationAttributeToEventType = new Map([
    ["webkitanimationend", "webkitAnimationEnd"],
    ["webkitanimationiteration", "webkitAnimationIteration"],
    ["webkitanimationstart", "webkitAnimationStart"],
    ["webkittransitionend", "webkitTransitionEnd"],
  ]);

  setup({ explicit_done: true });

  // basic pattern lifted from /html/webappapis/scripting/events/event-handler-all-global-events.html
  fetch("/interfaces/html.idl")
    .then(res => res.text())
    .then(htmlIDL => {
      const parsedHTMLIDL = WebIDL2.parse(htmlIDL);
      const globalEventHandlers = parsedHTMLIDL.find(
        idl => idl.name === "GlobalEventHandlers"
      );

      // onerror is too special
      const names = globalEventHandlers.members
        .map(member => member.name)
        .filter(name => name !== "onerror");

      for (const name of names) {
        const withoutOn = name.substring(2);

        test(() => {
          const location = MathMLElement.prototype;
          assert_true(
            location.hasOwnProperty(name),
            `${location.constructor.name} has an own property named "${name}"`
          );

          assert_false(
            name in Element.prototype,
            `Element.prototype must not contain a "${name}" property`
          );
        }, `${name}: must be on the appropriate locations for GlobalEventHandlers`);

        test(() => {
          const location = document.createElementNS(
            "http://www.w3.org/1998/Math/MathML",
            "math"
          );

          assert_equals(
            location[name],
            null,
            `The default value of the property is null for a ${
              location.constructor.name
            } instance`
          );
        }, `${name}: the default value must be null`);

        test(() => {
          const div = document.getElementById("container");
          div.innerHTML = `<math ${name}="window.${name}Happened1 = true;"></math>`;
          const compiledHandler = div.firstElementChild[name];
          assert_equals(
            typeof compiledHandler,
            "function",
            `The ${name} property must be a function`
          );
          compiledHandler();
          assert_true(
            window[`${name}Happened1`],
            "Calling the handler must run the code"
          );
        }, `${name}: the content attribute must be compiled into a function as the corresponding property`);

        test(() => {
          const el = document.createElementNS(
            "http://www.w3.org/1998/Math/MathML",
            "math"
          );
          assert_equals(el[name], null, `The ${name} property must be null (no attribute)`);

          el.setAttribute(name, `window.${name}Happened2 = true;`);
          const compiledHandler = el[name];
          assert_equals(
            typeof compiledHandler,
            "function",
            `The ${name} property must be a function (set attribute)`
          );
          compiledHandler();
          assert_true(
            window[`${name}Happened2`],
            "Calling the handler must run the code (set attribute)"
          );

          window[`${name}Happened2`] = false;
          const clonedEl = el.cloneNode(true);
          const clonedCompiledHandler = clonedEl[name];
          assert_equals(
            typeof clonedCompiledHandler,
            "function",
            `The ${name} property must be a function (clone node)`
          );
          clonedCompiledHandler();
          assert_true(
            window[`${name}Happened2`],
            "Calling the handler must run the code (clone node)"
          );

          el.setAttribute(name, `window.${name}Happened3 = true;`);
          const newCompiledHandler = el[name];
          assert_equals(
            typeof newCompiledHandler,
            "function",
            `The ${name} property must be a function (modify attribute)`
          );
          newCompiledHandler();
          assert_true(
            window[`${name}Happened3`],
            "Calling the handler must run the code (modify attribute)"
          );

          el.removeAttribute(name);
          assert_equals(el[name], null, `The ${name} property must be null (remove attribute)`);
        }, `${name}: dynamic changes on the attribute`);

        test(() => {
          const element = document.createElementNS(
            "http://www.w3.org/1998/Math/MathML",
            "math"
          );
          let target = undefined;
          element[name] = (e) => { target = e.currentTarget; }
          let eventType = withoutOn;
          if (prefixedAnimationAttributeToEventType.has(eventType)) {
            eventType = prefixedAnimationAttributeToEventType.get(eventType);
          }
          element.dispatchEvent(new Event(eventType));
          assert_equals(target, element, "The event must be fired at the <math> element");
        }, `${name}: dispatching an Event at a <math> element must trigger element.${name}`);
      }

      done();
    });
</script>
